package wei.qiang.ye.serverconsumer.service;

import com.netflix.hystrix.contrib.javanica.annotation.HystrixCollapser;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixProperty;
import com.netflix.hystrix.contrib.javanica.cache.annotation.CacheResult;
import com.netflix.hystrix.contrib.javanica.command.AsyncResult;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Service;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.client.RestTemplate;
import wei.qiang.ye.serverconsumer.domain.User;

import java.util.Arrays;
import java.util.List;
import java.util.concurrent.Future;

@Service("userService")
public class UserService {
    private Logger log = LoggerFactory.getLogger(this.getClass());

    @Autowired
    private RestTemplate restTemplate;

    /**
     * 注解的fallbackMethod属性指定了被调用的方法不可用时的回调方法（服务熔断时的回调处理逻辑，即服务降级），
     * 这里为getUserDefault方法（必须与getUser方法的参数及返回值类型一致）
     *
     * 通过指定@HystrixCommand注解的commandKey、groupKey以及threadPoolKey属性可以设置命令名称、分组以及线程池划分
     * 通过设置命令组，Hystrix会根据组来组织和统计命令的告警、仪表盘等信息。
     * 默认情况下，Hystrix命令通过组名来划分线程池，即组名相同的命令放到同一个线程池里，
     * 如果通过threadPoolKey设置了线程池名称，则按照线程池名称划分。
     * @param id
     * @return
     */
    @HystrixCommand(fallbackMethod = "getUserDefault", commandKey = "getUserById", groupKey = "userGroup",
     threadPoolKey = "getUserThread")
    @CacheResult //开启Hystrix缓存 不推荐使用 Hystrix会将返回的User对象进行缓存，缓存的key默认为方法的所有参数，这里只有一个id参数，所以缓存的key为用户id。
    public User getUser(@PathVariable Long id) {
        return restTemplate.getForObject("http://Server-Provider/user/{id}", User.class, id);
    }

    /**
     * 在使用@HystrixCommand注解标注的方法中，除了HystrixBadRequestException异常外，别的异常都会触发服务降级。
     * 假如想指定某个异常不触发服务降级，可以使用@HystrixCommand注解的ignoreExceptions属性进行忽略。
     *
     * 此外，对于方法抛出的异常信息，我们可以在服务降级的方法中使用Throwable对象获取
     * @param id
     * @return
     */
    @HystrixCommand(fallbackMethod = "getUserDefault2", ignoreExceptions = NullPointerException.class)
    public User getUserDefault(Long id, Throwable e) {
        System.out.println(e.getMessage());
        String a = null;
        // 测试服务降级
        a.toString();

        User user = new User();
        user.setId(-1L);
        user.setUsername("defaultUser");
        user.setPassword("123456");
        return user;
    }

    public User getUserDefault2(Long id) {
        User user = new User();
        user.setId(-2L);
        user.setUsername("defaultUser2");
        user.setPassword("123456");
        return user;
    }

    public List<User> getUsers() {
        return this.restTemplate.getForObject("http://Server-Provider/user", List.class);
    }

    public String addUser() {
        User user = new User(1L, "mrbird", "123456");
        HttpStatus status = this.restTemplate.postForEntity("http://Server-Provider/user", user, null).getStatusCode();
        if (status.is2xxSuccessful()) {
            return "新增用户成功";
        } else {
            return "新增用户失败";
        }
    }

    public void updateUser() {
        User user = new User(1L, "mrbird", "123456");
        this.restTemplate.put("http://Server-Provider/user", user);
    }

    public void deleteUser(@PathVariable Long id) {
        this.restTemplate.delete("http://Server-Provider/user/{1}", id);
    }


    /**
     * batchMethod属性指定了批量处理的方法为下面定义的findUserBatch方法，timerDelayInMilliseconds的值为100（毫秒），
     * 意思是在100毫秒这个时间范围内的所有对findUser的调用，都将被合并为一个批量处理操作，
     * 进行批量处理操作的方法就是findUserBatch。
     *
     * 合并请求后的消耗不一定比未合并的时间消耗少
     * 实际中是否该使用Hystrix的请求合并功能，需结合实际需求进行抉择
     * @param id
     * @return
     */
    @HystrixCollapser(batchMethod = "findUserBatch", collapserProperties = {
            @HystrixProperty(name = "timerDelayInMilliseconds", value = "100")
    })
    public Future<User> findUser(Long id) {
        //这里的代码并不会执行
        log.info("获取单个用户信息");
        return new AsyncResult<User>() {
            @Override
            public User invoke() {
                return restTemplate.getForObject("http://Server-Provider/user/{id}", User.class, id);
            }
        };
    }

    @HystrixCommand
    public List<User> findUserBatch(List<Long> ids) {
        log.info("批量获取用户信息,ids: " + ids);
        User[] users = restTemplate.getForObject("http://Server-Provider/user/users?ids={1}", User[].class, StringUtils.join(ids, ","));
        return Arrays.asList(users);
    }
}